#include "./sensor.h"
#include "../xlib/gpio.h"
#include "../rtos/mutex.h"
#include "../xlib/adc.h"
#include "../pub_driver/i2c.h"
#include "../cfg/heat_info.h"

namespace heat_driver {

Sensor sensor;

namespace sensor_impl {

using namespace cfg;
using namespace stm32f1;
using namespace rtos::lazy;
using namespace pub_driver;

using PL = gpio::PB<3>;
using CP = gpio::PB<4>;
using Q7 = gpio::PB<5>;

using ADC = gpio::PC<2>;

using SDA = gpio::PB<7>;
using SCL = gpio::PB<8>;
using RDY = gpio::PB<6>;
static constexpr uint8_t ADDR = 0x48;
using ADS1115 = I2cInterface<ADDR, SCL, SDA>;

using FLOW = gpio::PB<2>;
using LEAK = gpio::PC<3>;
using WASTE = gpio::PC<1>;
using TANK_LOW = gpio::PC<5>;
using TANK_HIGH = gpio::PC<4>;
using HOT_LOW = gpio::PC<7>;
using HOT_HIGH = gpio::PC<6>;


static Mutex mutex;
static uint16_t adcBuf[1];
static uint16_t flowCount;

}

using namespace sensor_impl;


void Sensor::init() {
    mutex.init();

    rcc::enable<
        PL, CP, Q7, ADC, adc::Adc1, dma::DmaSel<adc::Adc1::BASE_VAL>::CHAN,
        SDA, SCL, RDY, FLOW, LEAK, WASTE, TANK_LOW, TANK_HIGH, HOT_LOW, HOT_HIGH
    >();
    rcc::adc_prescaler_init();
    gpio::config<
        gpio::Cfg<PL, gpio::Mode::OUT_50MHz_PP>,
        gpio::Cfg<CP, gpio::Mode::OUT_50MHz_PP>,
        gpio::Cfg<Q7, gpio::Mode::INPUT_FLOAT>,
        gpio::Cfg<ADC, gpio::Mode::INPUT_ANALOG>,

        gpio::Cfg<SDA, gpio::Mode::OUT_50MHz_OD>,
        gpio::Cfg<SCL, gpio::Mode::OUT_50MHz_OD>,
        gpio::Cfg<RDY, gpio::Mode::INPUT_FLOAT>,

        gpio::Cfg<FLOW, gpio::Mode::INPUT_FLOAT>,
        gpio::Cfg<LEAK, gpio::Mode::INPUT_FLOAT>,
        gpio::Cfg<WASTE, gpio::Mode::INPUT_FLOAT>,
        gpio::Cfg<TANK_LOW, gpio::Mode::INPUT_FLOAT>,
        gpio::Cfg<TANK_HIGH, gpio::Mode::INPUT_FLOAT>,
        gpio::Cfg<HOT_LOW, gpio::Mode::INPUT_FLOAT>,
        gpio::Cfg<HOT_HIGH, gpio::Mode::INPUT_FLOAT>
    >();
    adc::config<adc::Adc1, 12>(adcBuf);

    AFIO->EXTICR[0] = AFIO->EXTICR[0] & (~(0x0F << 4)) | (0x01 << 4);
    EXTI->IMR |= 0x01 << 1;
    EXTI->FTSR |= 0x01 << 1;
    NVIC_EnableIRQ(EXTI1_IRQn);
}

uint16_t Sensor::adc_value() {
    return adcBuf[0];
}

uint8_t Sensor::read() {
    mutex.lock();

    uint8_t val = 0;
    PL::clr();
    PL::set();
    for (uint8_t i = 0; i < 8; i ++) {
        val <<= 1;
        if (Q7::read()) {
            val ++;
        }
        CP::set();
        CP::clr();
    }

    mutex.un_lock();

    return val;
}

uint16_t Sensor::flow_count() {
    return flowCount;
}

uint16_t Sensor::flow() {
    return flowCount * 1000 / heatInfo->flow;
}

void Sensor::flow_reset() {
    flowCount = 0;
}

bool Sensor::is_tank_high() {
    return TANK_HIGH::read();
}

bool Sensor::is_tank_low() {
    return TANK_LOW::read();
}

bool Sensor::is_leak() {
    return LEAK::read();
}

bool Sensor::is_waste_full() {
    return WASTE::read();
}

bool Sensor::is_hot_high() {
    return HOT_HIGH::read();
}

bool Sensor::is_hot_low() {
    return HOT_LOW::read();
}

#include "../pub_driver/trmap.h"

static constexpr uint8_t CONVERSION_REG = 0;
static constexpr uint8_t CONFIG_REG = 1;

static constexpr uint16_t CH1_CFG = 0x8483;
static constexpr uint16_t CH2_CFG = 0xB483;

const uint32_t TR_COUNT = sizeof (TRMap) / sizeof (TRMap[0]);
const uint32_t R_MIN = TRMap[TR_COUNT - 1];
const uint32_t R_MAX = TRMap[0];
constexpr uint32_t NOT_FOUND_INDEX = static_cast<uint32_t>(-1);

static uint32_t find(uint32_t val) {
    if ((val > R_MAX) || (val < R_MIN)) {
        return NOT_FOUND_INDEX;
    }
    uint32_t left = 0;
    uint32_t right = TR_COUNT - 1;

    while ((right - left) > 1) {
        uint32_t index = (left + right) / 2;
        uint32_t cv = TRMap[index];
        if (val > cv) {
            right = index;
        } else if (val < cv) {
            left = index;
        } else {
            return index;
        }
    }
    return left;
}

static bool read(uint16_t cfg, uint32_t &val) {
    uint16_t v;
    bool r = ADS1115::write(CONFIG_REG, cfg);
    if (!r) {
        return false;
    }

    r = ADS1115::read(CONVERSION_REG, v);
    if (!r) {
        return false;
    }

    uint32_t tmp = v * 2048 / 32768;
    val = 20000 * tmp / (1800 - tmp);

    return true;
}

bool read_find(uint16_t cfg, int16_t &val) {
    uint32_t tmp;
    bool ret = read(cfg, tmp);
    if (!ret) {
        return false;
    }
    uint32_t index = find(tmp);
    if (index == NOT_FOUND_INDEX) {
        return false;
    }
    val = static_cast<int16_t>(index) - 40;

    return true;
}

void Sensor::read_temp() {
    m_water_temp_ok = read_find(CH1_CFG, m_water_temp);

    m_steam_temp_ok = read_find(CH2_CFG, m_steam_temp);
}

}

using namespace heat_driver;

extern "C"
void EXTI1_IRQHandler() {
    if (0x00 == (EXTI->PR & (0x01 << 1))) {
        return;
    }
    EXTI->PR = 0x01 << 1;
    flowCount ++;
}

